package implementation.client;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import implementation.shared.EPacketParam;
import implementation.shared.EPacketType;
import implementation.shared.INotification;
import implementation.shared.IPacket;
import implementation.shared.SharedFactory;
import implementation.shared.stubs.IForumStub;
import implementation.shared.stubs.IPostStub;
import implementation.shared.stubs.IThreadStub;
import implementation.shared.stubs.IUserStub;

public class ServerStub implements IServerStub {

	private static final String MESSAGE_SERVER_CONNECTION_ERROR = "Server connection error.";
	protected IClientConnectionManager _connectionManager;
	protected String _errorMessage = "";
	protected ConcurrentLinkedQueue<IPacket> _inbox;
	protected ConcurrentLinkedQueue<IPacket> _notificationInbox;
	protected Thread _listener;

	public ServerStub() {
		this._connectionManager = CClientFactory
				.getConnectionManagerSingelton();
		this._inbox = new ConcurrentLinkedQueue<IPacket>();
		this._notificationInbox = new ConcurrentLinkedQueue<IPacket>();
		this._listener = new Thread(new Runnable() {

			public void run() {
				try {
					while (true) {
						IPacket packet;
						packet = _connectionManager.recieve();
						if (packet.getType() == EPacketType.ENotification) {
							_notificationInbox.add(packet);
							synchronized (_notificationInbox) {
								_notificationInbox.notify();
							}
						} else {
							_inbox.add(packet);
							synchronized (_inbox) {
								_inbox.notify();
							}
						}
					}
				} catch (InterruptedException e) {
				}
			}
		});

	}

	@Override
	public boolean connect(String host, int port) {
		if (this._connectionManager.connect(host, port)) {
			this._listener.start();
			return true;
		}
		return false;
	}

	private IPacket getPacket(Queue<IPacket> inbox) {
		IPacket packet = null;
		synchronized (inbox) {
			while (inbox.isEmpty()) {
				try {
					inbox.wait();
				} catch (InterruptedException e) {
					return null;
				}
			}
			packet = inbox.poll();
		}
		return packet;
	}

	@Override
	public boolean login(String username, String password) {

		IPacket loginPacket = SharedFactory.createPacket(EPacketType.ELogin);
		loginPacket.addParam(EPacketParam.EUsername, username);
		loginPacket.addParam(EPacketParam.EPassword, password);

		return this.booleanOperation(loginPacket);
	}

	@Override
	public boolean register(String username, String password) {
		IPacket registerPacket = SharedFactory
				.createPacket(EPacketType.ERegister);
		registerPacket.addParam(EPacketParam.EUsername, username);
		registerPacket.addParam(EPacketParam.EPassword, password);
		return this.booleanOperation(registerPacket);
	}

	@Override
	public boolean addFriend(String friendName) {
		IPacket friendPacket = SharedFactory
				.createPacket(EPacketType.EAddFriend);
		friendPacket.addParam(EPacketParam.EUsername, friendName);
		return this.booleanOperation(friendPacket);
	}

	@Override
	public IPostStub getPost(int postID) {
		IPacket postPacket = SharedFactory.createPacket(EPacketType.EGetPost);
		postPacket.addParam(EPacketParam.EID, postID);
		IPostStub postStub = (IPostStub) this.getAnswer(postPacket,
				EPacketParam.EPost);
		return postStub;

	}

	@Override
	public IThreadStub getThread(int threadID) {
		IPacket threadPacket = SharedFactory
				.createPacket(EPacketType.EGetThread);
		threadPacket.addParam(EPacketParam.EID, threadID);
		return (IThreadStub) this.getAnswer(threadPacket, EPacketParam.EThread);

	}

	@Override
	public IForumStub getForum(int forumID) {
		IPacket forumPacket = SharedFactory.createPacket(EPacketType.EGetForum);
		forumPacket.addParam(EPacketParam.EID, forumID);
		return (IForumStub) this.getAnswer(forumPacket, EPacketParam.EForum);

	}

	@Override
	public boolean editPost(int postID, String topic, String content) {

		IPacket postPacket = SharedFactory.createPacket(EPacketType.EEditPost);
		postPacket.addParam(EPacketParam.EID, postID);
		postPacket.addParam(EPacketParam.ETopic, topic);
		postPacket.addParam(EPacketParam.EContent, content);
		return this.booleanOperation(postPacket);
	}

	@Override
	public boolean addPost(int threadId, String topic, String content) {
		IPacket postPacket = SharedFactory.createPacket(EPacketType.EAddPost);
		postPacket.addParam(EPacketParam.EID, threadId);
		postPacket.addParam(EPacketParam.ETopic, topic);
		postPacket.addParam(EPacketParam.EContent, content);
		return this.booleanOperation(postPacket);
	}

	@Override
	public boolean addThread(int forumId, String topic, String content) {
		IPacket postPacket = SharedFactory.createPacket(EPacketType.EAddThread);
		postPacket.addParam(EPacketParam.EID, forumId);
		postPacket.addParam(EPacketParam.ETopic, topic);
		postPacket.addParam(EPacketParam.EContent, content);
		return this.booleanOperation(postPacket);
	}

	@SuppressWarnings("unchecked")
	@Override
	public Collection<IForumStub> getForums() {
		IPacket forumsPacket = SharedFactory
				.createPacket(EPacketType.EGetAllForums);
		return (Collection<IForumStub>) this.getAnswer(forumsPacket,
				EPacketParam.ECollection);
	}

	@Override
	public Collection<IUserStub> getFriends() {
		IPacket friendsPacket = SharedFactory
				.createPacket(EPacketType.EGetFriends);
		return (Collection<IUserStub>) this.getAnswer(friendsPacket,
				EPacketParam.ECollection);
	}

	@Override
	public boolean removeFriend(int friendID) {
		IPacket friendPacket = SharedFactory
				.createPacket(EPacketType.ERemoveFriend);
		friendPacket.addParam(EPacketParam.EID, friendID);
		return this.booleanOperation(friendPacket);
	}

	@Override
	public boolean removeThread(int threadID) {
		IPacket postPacket = SharedFactory
				.createPacket(EPacketType.ERemoveThread);
		postPacket.addParam(EPacketParam.EID, threadID);
		return this.booleanOperation(postPacket);
	}

	@Override
	public boolean logout() {
		return this.booleanOperation(SharedFactory
				.createPacket(EPacketType.ELogout));

	}

	@Override
	public boolean removePost(int postID) {
		IPacket postPacket = SharedFactory
				.createPacket(EPacketType.ERemovePost);
		postPacket.addParam(EPacketParam.EID, postID);
		return this.booleanOperation(postPacket);
	}

	@Override
	public String getErrorMessage() {
		return this._errorMessage;
	}

	@Override
	public void close() {
		this._listener.interrupt();
		try {
			this._listener.join();
		} catch (InterruptedException e) {
		}
		this._connectionManager.close();
	}

	private void setErrorMessage(IPacket packet) {
		this._errorMessage = (String) packet.getParam(EPacketParam.EMessage);
	}

	private void setErrorMessage(String errMsg) {
		this._errorMessage = errMsg;
	}

	private boolean send(IPacket packet) {
		boolean sent = this._connectionManager.send(packet);
		if (sent) {
			return true;
		}
		this._errorMessage = MESSAGE_SERVER_CONNECTION_ERROR;
		return false;
	}

	@Override
	public Collection<IThreadStub> getThreads(int forumID) {
		IPacket threadsPacket = SharedFactory
				.createPacket(EPacketType.EGetAllThreads);
		threadsPacket.addParam(EPacketParam.EID, forumID);
		return (Collection<IThreadStub>) this.getAnswer(threadsPacket,
				EPacketParam.ECollection);
	}

	@Override
	public Collection<IPostStub> getPosts(int threadID) {
		IPacket postsPacket = SharedFactory
				.createPacket(EPacketType.EGetAllPosts);
		postsPacket.addParam(EPacketParam.EID, threadID);
		ArrayList<IPostStub> col = new ArrayList<IPostStub>();
		return (Collection<IPostStub>) this.getAnswer(postsPacket,
				EPacketParam.ECollection);
	}

	private Object getAnswer(IPacket packet, EPacketParam answerParam) {
		if (!this.send(packet)) {
			this.setErrorMessage(MESSAGE_SERVER_CONNECTION_ERROR);
			return null;
		}
		IPacket answer = this.getPacket(this._inbox);
		if (answer == null) {
			this.setErrorMessage(MESSAGE_SERVER_CONNECTION_ERROR);
			return null;
		}
		this.setErrorMessage(answer);
		return answer.getParam(answerParam);
	}

	private boolean booleanOperation(IPacket packet) {
		boolean sent = this._connectionManager.send(packet);
		if (!sent) {
			this._errorMessage = MESSAGE_SERVER_CONNECTION_ERROR;
			return false;
		}
		IPacket answer = this.getPacket(this._inbox);
		if (answer.isSuccessful()) {
			return true;
		}
		this.setErrorMessage(answer);
		return false;
	}

	@Override
	public INotification getNotification() {
		IPacket notificationPacket = this.getPacket(this._notificationInbox);
		if (notificationPacket == null) {
			return null;
		}
		INotification n = (INotification) notificationPacket.getParam(EPacketParam.ENotification);
		return n;
	}

}
